using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Xml;
using System.Xml.Schema;

namespace Jeffrey.XFramework.XCrawl
{
    public class CrawlOrder
    {
        public  static readonly Encoding PrimEncoding = Encoding.GetEncoding("ISO-8859-1");
        private static readonly Char[] s_Trim = {'\n','\t',' '};

        public CrawlOrder()
        {
            m_ConfigPath = CrawlToolKits.GetAbsolutePath(CrawlController.s_Directory_Path, @"xuthus.config");
            Validity   = true;

            m_SourceLinks = new List<String>();

            m_Filters      = new List<ProcessorInfo>();
            m_PreFetchers  = new List<ProcessorInfo>();
            m_Fetchers     = new List<ProcessorInfo>();
            m_PostFetchers = new List<ProcessorInfo>();
            m_Extractors   = new List<ProcessorInfo>();
            m_Storages     = new List<ProcessorInfo>();
        }

        public Boolean Initialize()
        {
            return LoadConfigFile();
        }

        internal Boolean LoadConfigFile()
        {
            m_SourceLinks.Clear();
            ReleaseProcessorInfo();

            m_Settings = new XmlReaderSettings();
            m_Settings.ProhibitDtd = false;
            m_Settings.ValidationType = ValidationType.DTD;

            m_Settings.ValidationEventHandler += new ValidationEventHandler(ValidationCallBack);

            try
            {
                m_Reader = XmlReader.Create(m_ConfigPath, m_Settings);
                ParseConfigXml();
            }
            catch (Exception)
            {
                return false;
            }

            m_Reader.Close();

            return Validity;
        }

        internal Boolean SaveConfigFile(CrawlController cc)
        {
            try
            {
                XmlDocument xmldoc = new XmlDocument();
                xmldoc.AppendChild(xmldoc.CreateXmlDeclaration("1.0", Encoding.Default.WebName, null));
                xmldoc.AppendChild(xmldoc.CreateDocumentType("xuthus", null, @"config.dtd", null));
                XmlElement xroot = xmldoc.CreateElement("xuthus");

                //create xsourcelinks node...
                XmlElement xlink = xmldoc.CreateElement("xsourcelinks");

                //create xprocess node...
                XmlElement xpro = xmldoc.CreateElement("xprocess");

                //create xspider node...
                XmlElement xspi = xmldoc.CreateElement("xspider");

                //create xsocket node...
                XmlElement xsoc = xmldoc.CreateElement("xsocket");

                //create xlogger node...
                XmlElement xlog = xmldoc.CreateElement("xlogger");

                //create xsystem node..
                XmlElement xsys = xmldoc.CreateElement("xsystem");

                xroot.AppendChild(xlink);
                xroot.AppendChild(xpro);
                xroot.AppendChild(xspi);
                xroot.AppendChild(xsoc);
                xroot.AppendChild(xlog);
                xroot.AppendChild(xsys);

                //add links
                String urls = String.Empty;
                foreach (String url in m_SourceLinks)
                    urls += url + ";";

                xlink.AppendChild(xmldoc.CreateTextNode(urls));

                //add filters
                foreach (Filter filter in cc.m_Filters)
                {
                    XmlElement xe = xmldoc.CreateElement("filter");

                    xe.SetAttribute("name", filter.Name);
                    xe.SetAttribute("description", filter.Description);

                    XmlElement path = xmldoc.CreateElement("path");
                    path.AppendChild(xmldoc.CreateTextNode(filter.Path));
                    XmlElement classname = xmldoc.CreateElement("classname");
                    classname.AppendChild(xmldoc.CreateTextNode(filter.ClassName));

                    xe.AppendChild(path);
                    xe.AppendChild(classname);

                    xpro.AppendChild(xe);
                }

                //add Processors...
                foreach (Processor proc in cc.UriProcessChain)
                    CreateProcessorNode(xmldoc, xpro, proc);

                //set spiders attributes...
                xspi.SetAttribute("number", m_SpiderNum.ToString());
                xspi.SetAttribute("totalpages", (m_TotalPages / (10 * 1000)).ToString());
                xspi.SetAttribute("depth", m_Depth.ToString());
                xspi.SetAttribute("encoding", m_Encoding.WebName);
                xspi.SetAttribute("interval", m_Interval.ToString());

                //set the socket attributes...
                xsoc.SetAttribute("blockedretry", m_BlockedRetry.ToString());
                xsoc.SetAttribute("dnscache", m_DnsCache.ToString());
                xsoc.SetAttribute("keepalive", m_KeepAlive.ToString());
                xsoc.SetAttribute("timeout", (m_TimeOut / 1000).ToString());

                //set the logger file...
                xlog.SetAttribute("file", String.Empty);

                //set the system attributes...
                xsys.SetAttribute("enablegc", m_GC.ToString());

                xmldoc.AppendChild(xroot);
                xmldoc.Save(@"xuthus.config");
            }
            catch (Exception)
            {
                return false;
            }

            return true;
        }

        private void CreateProcessorNode(XmlDocument xmldoc, XmlNode xpro, Processor proc)
        {
            XmlElement xe = xmldoc.CreateElement(proc.Chain.Name.ToString().ToLower());

            xe.SetAttribute("name", proc.Name);
            xe.SetAttribute("description", proc.Description);
            XmlElement path = xmldoc.CreateElement("path");
            path.AppendChild(xmldoc.CreateTextNode(proc.Path));
            XmlElement classname = xmldoc.CreateElement("classname");
            classname.AppendChild(xmldoc.CreateTextNode(proc.ClassName));

            xe.AppendChild(path);
            xe.AppendChild(classname);

            if (proc.Filter != null)
            {
                XmlElement attachedfilter = xmldoc.CreateElement("attachedfilter");
                attachedfilter.AppendChild(xmldoc.CreateTextNode(proc.Filter.Name));
                xe.AppendChild(attachedfilter);
            }

            xpro.AppendChild(xe);
        }

        //SourceLinks
        public List<String> SourceLinks
        {
            get { return m_SourceLinks; }
        }

        //Config Sutffs
        public Encoding DefaultEncoding
        {
            get { return m_Encoding; }
            set { m_Encoding = value; }
        }
        public Int32 TotalPages
        {
            get { return m_TotalPages; }
            set { m_TotalPages = value; }
        }
        public Int32 Depth
        {
            get { return m_Depth; }
            set { m_Depth = value; }
        }
        public Int32 TimeOut
        {
            get { return m_TimeOut; }
            set { m_TimeOut = value; }
        }
        public Int32 Interval
        {
            get { return m_Interval; }
            set { m_Interval = value; }
        }
        public Int32 BlockedRetry
        {
            get { return m_BlockedRetry; }
            set { m_BlockedRetry = value; }
        }
        public Boolean DnsCache
        {
            get { return m_DnsCache; }
            set { m_DnsCache = value; }
        }
        public Boolean KeepAlive
        {
            get { return m_KeepAlive; }
            set { m_KeepAlive = value; }
        }
        public Boolean GC
        {
            get { return m_GC; }
            set { m_GC = value; }
        }

        public void ReleaseProcessorInfo()
        {
            m_Filters.Clear();
            m_PreFetchers.Clear();
            m_Fetchers.Clear();
            m_PostFetchers.Clear();
            m_Extractors.Clear();
            m_Storages.Clear();
        }

        protected void ParseConfigXml()
        {
            Validity = true;
            String ElementName = null;

            while (m_Reader.Read())
            {
                switch (m_Reader.NodeType)
                {
                    case XmlNodeType.Element :
                        {
                            switch (m_Reader.Name)
                            {
                                case "xsourcelinks": ElementName = "xsourcelinks";                break;
                                case "filter":    m_Filters.Add(GetProcessInfo(true));            break;
                                case "prefetchchain":  m_PreFetchers.Add(GetProcessInfo(false));  break;
                                case "fetchchain": m_Fetchers.Add(GetProcessInfo(false));         break;
                                case "postfetchchain": m_PostFetchers.Add(GetProcessInfo(false)); break;
                                case "extractchain": m_Extractors.Add(GetProcessInfo(false));     break;
                                case "storechain": m_Storages.Add(GetProcessInfo(false));         break;
                                case "xprocess":  ElementName = "xprocess";                       break;
                                case "xspider":
                                    {
                                        ElementName = "xspider";
                                        if (m_Reader.HasAttributes)
                                        {
                                            for (Int32 i = 0; i < m_Reader.AttributeCount; i++)
                                            {
                                                m_Reader.MoveToAttribute(i);
                                                switch (m_Reader.Name)
                                                {
                                                    case "number": m_SpiderNum = Int32.Parse(m_Reader.Value); break;
                                                    case "totalpages": m_TotalPages = Int32.Parse(m_Reader.Value) * 10 * 1000; break;
                                                    case "depth": m_Depth = Int32.Parse(m_Reader.Value); break;
                                                    case "encoding":
                                                        {
                                                            try
                                                            {
                                                                m_Encoding = Encoding.GetEncoding(m_Reader.Value);
                                                            }
                                                            catch (ArgumentException)
                                                            {
                                                                m_Encoding = Encoding.Default;
                                                            }
                                                        } break;
                                                    case "interval": m_Interval = Int32.Parse(m_Reader.Value); break;
                                                    default: break;
                                                }
                                            }
                                            m_Reader.MoveToElement();
                                        }
                                    }
                                    break;
                                case "xsocket":
                                    {
                                        ElementName = "xsocket";
                                        if (m_Reader.HasAttributes)
                                        {
                                            for (Int32 i = 0; i < m_Reader.AttributeCount; i++)
                                            {
                                                m_Reader.MoveToAttribute(i);
                                                switch (m_Reader.Name)
                                                {
                                                    case "blockedretry": m_BlockedRetry = Int32.Parse(m_Reader.Value); break;
                                                    case "dnscache": m_DnsCache = Boolean.Parse(m_Reader.Value);       break;
                                                    case "keepalive": m_KeepAlive = Boolean.Parse(m_Reader.Value);     break;
                                                    case "timeout": m_TimeOut = Int32.Parse(m_Reader.Value) * 1000;    break;
                                                    default: break;
                                                }
                                            }
                                            m_Reader.MoveToElement();
                                        }
                                    }
                                    break;
                                case "xlogger":
                                    {
                                        ElementName = "xlogger";
                                        if (m_Reader.HasAttributes)
                                        {
                                            m_Reader.MoveToAttribute(0);

                                            m_LoggerPath = m_Reader.Value;

                                            m_Reader.MoveToElement();
                                        }
                                    }
                                    break;
                                case "xsystem":
                                    {
                                        ElementName = "xsystem";
                                        if (m_Reader.HasAttributes)
                                        {
                                            for (Int32 i = 0; i < m_Reader.AttributeCount; i++)
                                            {
                                                m_Reader.MoveToAttribute(i);
                                                switch (m_Reader.Name)
                                                {
                                                    case "enablegc": m_GC = Boolean.Parse(m_Reader.Value); break;
                                                    default: break;
                                                }
                                            }
                                            m_Reader.MoveToElement();
                                        }
                                    }
                                    break;
                                default: break;
                            }
                        }
                        break;
                    case XmlNodeType.Text :
                        {
                            switch (ElementName)
                            {
                                case "xsourcelinks":
                                    {
                                        String[] SourceLink = m_Reader.Value.Split(new Char[]{';'});

                                        foreach (String str in SourceLink)
                                        {
                                            String trimed = str.Trim(s_Trim);
                                            if(trimed != String.Empty)
                                                m_SourceLinks.Add(trimed);
                                        }
                                    }
                                    break;
                                default: break;
                            }
                        }
                        break;
                    default: break;
                }
            }

            if (Validity)
                Logger.LogInfo("Config file checked : No errors!");
        }

        protected ProcessorInfo GetProcessInfo(Boolean isFilter)
        {
            XmlReader sub_Reader = null;
            ProcessorInfo prinfo = new ProcessorInfo();

            if (m_Reader.HasAttributes)
            {
                for (Int32 i = 0; i < m_Reader.AttributeCount; i++)
                {
                    m_Reader.MoveToAttribute(i);
                    switch (m_Reader.Name)
                    {
                        case "name": prinfo.m_Name = m_Reader.Value; break;
                        case "description": prinfo.m_Description = m_Reader.Value; break;
                        default: break;
                    }
                }
                m_Reader.MoveToElement();
            }

            sub_Reader = m_Reader.ReadSubtree();

            sub_Reader.ReadToFollowing("path");
            prinfo.m_Path = sub_Reader.ReadElementContentAsString().Trim(s_Trim);
            sub_Reader.ReadToFollowing("classname");
            prinfo.m_ClassName = sub_Reader.ReadElementContentAsString().Trim(s_Trim);

            if (!isFilter)
            {
                sub_Reader.ReadToFollowing("attachedfilter");
                prinfo.m_Attachedfilter = sub_Reader.ReadElementContentAsString().Trim(s_Trim);
            }

            sub_Reader.Close();

            return prinfo;
        }

        private void ValidationCallBack(Object sender, ValidationEventArgs e)
        {
            Validity = false;
            Logger.LogInfo("Validation Error when check Config file:  " + e.Message);
        }

        private  String m_ConfigPath;
        internal String m_LoggerPath;

        private XmlReaderSettings m_Settings;
        private XmlReader         m_Reader;
        private List<String>      m_SourceLinks;

        internal List<ProcessorInfo> m_Filters;
        internal List<ProcessorInfo> m_PreFetchers;
        internal List<ProcessorInfo> m_Fetchers;
        internal List<ProcessorInfo> m_PostFetchers;
        internal List<ProcessorInfo> m_Extractors;
        internal List<ProcessorInfo> m_Storages;

        internal Int32 m_SpiderNum = 5;

        //Propertise to setting...
        //the total number of pages to crawl
        internal Int32 m_TotalPages = 1 * 10 * 1000;
        //the depth of pages to crawl
        internal Int32 m_Depth = 3;
        //the timeout for socket sending and receiving
        internal Int32 m_TimeOut = 10 * 1000; //ms
        //the interval time for spider between two pages fetching
        internal Int32 m_Interval = 100;
        //the retry times when the TCP/IP buffer blocked...the interval is 500ms.
        internal Int32 m_BlockedRetry = 3;
        //whether or not to use the dns cache
        internal Boolean m_DnsCache = true;
        //the default encoding to fetch the pages
        internal Encoding m_Encoding = Encoding.Default;
        //whether or not to use the persistent connection
        internal Boolean m_KeepAlive = true;
        //whether or not to enable the GC
        internal Boolean m_GC = false;

        private Boolean Validity = true;
    }

    public struct ProcessorInfo
    {
        public String Path { get { return m_Path; } set { m_Path = value; } }
        public String ClassName { get { return m_ClassName; } set { m_ClassName = value; } }
        public String Name { get { return m_Name; } set { m_Name = value; } }
        public String Description { get { return m_Description; } set { m_Description = value; } }
        //this property do not use in Filter
        public String Attachedfilter { get { return m_Attachedfilter; } set { m_Attachedfilter = value; } } 

        internal String m_Path;
        internal String m_ClassName;
        internal String m_Name;
        internal String m_Description;
        //this property do not use in Filter
        internal String m_Attachedfilter;
    }
}
